﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Net.Nntp.Cache
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Data.SqlServerCe;
    using System.Diagnostics.CodeAnalysis;

    /// <summary>Provides the means to efficiently perform article queries multiple times.</summary>
    /// <remarks>
    /// <para>All methods set up the query infrastructure in the first call and reuse it for subsequent calls. This
    /// makes subsequent calls considerably faster.</para>
    /// <para>Each query is always specific to the group from which an object of this class was obtained.</para>
    /// </remarks>
    /// <threadsafety static="true" instance="false"/>
    public sealed class QueryCache : IDisposable
    {
        private readonly Group group;
        private InboundArticleFilter articleFilter = InboundArticleFilter.NullFilter;
        private string cachedOriginalsQueryBody;
        private string cachedFollowupsQueryBody;
        private SqlCeCommand cachedOriginalInboundArticlesCommand;
        private SqlCeCommand cachedThreadCountCommand;

        /// <summary>Gets the group this query cache is associated with.</summary>
        public IGroup Group
        {
            get { return this.group; }
        }

        /// <summary>Gets all articles that do not reference another article matching the criteria defined by the
        /// <see cref="IGroup.InboundArticleFilter"/> property of the group this object was obtained from.</summary>
        /// <exception cref="InvalidOperationException"><see cref="Cache.Dispose"/> has been called on the parent
        /// <see cref="Cache"/> object.</exception>
        /// <remarks>The documented exceptions are never thrown from this method but from the
        /// <see cref="IEnumerator.MoveNext"/> method.</remarks>
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is an expensive operation.")]
        public IEnumerable<IInboundArticle> GetOriginalInboundArticles()
        {
            this.UpdateQueryBodies();

            if (this.articleFilter == InboundArticleFilter.NullFilter)
            {
                return InboundArticleSelector.GetArticles(this.group, null);
            }
            else
            {
                if (this.cachedOriginalInboundArticlesCommand == null)
                {
                    this.cachedOriginalInboundArticlesCommand = this.group.CreateOriginalInboundArticlesCommand(
                        this.articleFilter, this.cachedOriginalsQueryBody, this.cachedFollowupsQueryBody);
                }

                return this.group.GetOriginalInboundArticlesCore(this.cachedOriginalInboundArticlesCommand, false);
            }
        }

        /// <summary>Gets the number of articles that do not reference another article and match the criteria defined
        /// by the <see cref="IGroup.InboundArticleFilter"/> property of the group this object was obtained from.</summary>
        /// <exception cref="InvalidOperationException"><see cref="Cache.Dispose"/> has been called on the parent
        /// <see cref="Cache"/> object.</exception>
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This is an expensive operation.")]
        public int GetThreadCount()
        {
            this.UpdateQueryBodies();

            if (this.cachedThreadCountCommand == null)
            {
                this.cachedThreadCountCommand = this.group.CreateThreadCountCommand(
                    this.articleFilter, this.cachedOriginalsQueryBody, this.cachedFollowupsQueryBody);
            }

            return Mews.Net.Nntp.Cache.Group.GetThreadCountCore(this.cachedThreadCountCommand, false);
        }

        /// <summary>See <see cref="IDisposable.Dispose"/>.</summary>
        public void Dispose()
        {
            this.DisposeCommands();
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal QueryCache(Group group)
        {
            this.group = group;
            Mews.Net.Nntp.Cache.Group.FormatQueryBodies(
                this.articleFilter, out this.cachedOriginalsQueryBody, out this.cachedFollowupsQueryBody);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private void UpdateQueryBodies()
        {
            if (this.articleFilter != this.group.InboundArticleFilter)
            {
                this.articleFilter = this.group.InboundArticleFilter;
                Mews.Net.Nntp.Cache.Group.FormatQueryBodies(
                    this.articleFilter, out this.cachedOriginalsQueryBody, out this.cachedFollowupsQueryBody);
                this.DisposeCommands();
            }
        }

        private void DisposeCommands()
        {
            if (this.cachedThreadCountCommand != null)
            {
                this.cachedThreadCountCommand.Dispose();
                this.cachedThreadCountCommand = null;
            }

            if (this.cachedOriginalInboundArticlesCommand != null)
            {
                this.cachedOriginalInboundArticlesCommand.Dispose();
                this.cachedOriginalInboundArticlesCommand = null;
            }
        }
    }
}
